// GTA:San Andreas ped replacement example using plugin-sdk.
// This .ASI loads a file from disk and uses it to replace the player ped model on keypress.
// Should be happening dynamically, meow.

#pragma warning(disable: 4996)

#include <plugin.h>
#include <game_sa/CFileMgr.h>
#include <game_sa/RenderWare.h>
#include <game_sa/common.h>

template <typename retType, typename... Args>
struct sa_func_def
{
    typedef retType (__cdecl*function_t)( Args... );

    inline sa_func_def( DWORD ptr )
    {
        this->func_ptr = (void*)ptr;
    }

    inline retType operator () ( Args... args ) const
    {
        return ( (function_t)this->func_ptr )( args... );
    }

private:
    void *func_ptr;
};

// Define all the functions we will need.
static const sa_func_def <CBaseModelInfo*, const char*, unsigned int*> FindModelByName( 0x004C5940 );

// THX LINKY BOY. *cough* I mean LINK/2012.
static const sa_func_def <RwTexDictionary*, RwStream*> RwTexDictionaryStreamReadRockstar( 0x00731070 );

static RpClump* LoadModelFromGameDir( const char *fileName )
{
    // Load TXD beforehand, if available.
    RwTexDictionary *modelTextures = NULL;
    {
        std::string texturePath = std::string( CFileMgr::ms_rootDirName ) + fileName + ".txd";

        RwStream *textureStream = RwStreamOpen( rwSTREAMFILENAME, rwSTREAMREAD, (void*)texturePath.c_str() );

        if ( textureStream )
        {
            if ( RwStreamFindChunk( textureStream, 0x16, NULL, NULL ) )
            {
                modelTextures = RwTexDictionaryStreamReadRockstar( textureStream );
            }

            RwStreamClose( textureStream, NULL );
        }
    }

    if ( modelTextures )
    {
        RwTexDictionarySetCurrent( modelTextures );
    }

    std::string modelFileLocation = std::string( CFileMgr::ms_rootDirName ) + fileName + ".dff";

    RpClump *modelResult = NULL;

    RwStream *modelStream = RwStreamOpen( rwSTREAMFILENAME, rwSTREAMREAD, (void*)modelFileLocation.c_str() );

    if ( modelStream )
    {
        if ( RwStreamFindChunk( modelStream, 0x10, NULL, NULL ) )
        {
            modelResult = RpClumpStreamRead( modelStream );
        }

        RwStreamClose( modelStream, NULL );
    }

    if ( modelTextures )
    {
        RwTexDictionarySetCurrent( NULL );

        RwTexDictionaryDestroy( modelTextures );
    }

    return modelResult;
}

struct SAPedReplacementExample
{
    SAPedReplacementExample( void )
    {
        using namespace plugin;

        Events::initRwEvent += []( void )
        {

        };

        Events::shutdownRwEvent += []( void )
        {

        };

        Events::gameProcessEvent += []( void )
        {
            if ( KeyPressed( 'C' ) )
            {
                unsigned int playerModelIndex = -1;

                if ( CBaseModelInfo *playerModelBase = FindModelByName( "PLAYER", &playerModelIndex ) )
                {
                    // Has to be a clump model info
                    if ( playerModelBase->GetModelType() == MODEL_INFO_PED )
                    {
                        CClumpModelInfo *pedModelInfo = (CClumpModelInfo*)playerModelBase;

                        // Load a model from disk :)
                        if ( RpClump *newPedModel = LoadModelFromGameDir( "player" ) )
                        {
                            // Put it into the player model info.
                    
                            bool putSuccess = false;

                            // Let's actually free data from the local player.
                            CAnimBlendAssociation *animData = NULL;

                            CPed *playerPed = FindPlayerPed();

                            if ( playerPed )
                            {
                                RpClump *pedClump = (RpClump*)playerPed->m_pRwObject;

                                // We need to set this thing again.
                                animData = RpAnimBlendClumpExtractAssociations( pedClump );
                                
                                CTask *secTask = playerPed->m_pIntelligence->m_TaskMgr.GetTaskSecondary( 5 );

                                if ( secTask )
                                {
                                    secTask->MakeAbortable( playerPed, 2, NULL );
                                }

                                playerPed->DeleteRwObject();
                            }

                            pedModelInfo->DeleteRwObject();
                            pedModelInfo->SetClump( newPedModel );

                            putSuccess = true;

                            // Load the model for the player.
                            if ( playerPed )
                            {
                                playerPed->SetModelIndex( playerModelIndex );

                                RpClump *playerClump = (RpClump*)playerPed->m_pRwObject;

                                RpAnimBlendClumpGiveAssociations( playerClump, animData );
                            }

                            if ( !putSuccess )
                            {
                                RpClumpDestroy( newPedModel );
                            }
                        }
                    }
                }
            }
        };
    }

    ~SAPedReplacementExample( void )
    {

    }

} ped_rpl_plugin;